# 1.数据预处理需求：
# 必须利用sklearn.datasets模块中的load_digits函数来加载手写数字数据集。
# 数据集的特征值需进行标准化处理，推荐使用StandardScaler工具。
# 数据集需被明确划分为训练集和测试集，其中测试集应占总数据的20%，且划分过程中随机种子需设置为42以确保结果的一致性。
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import load_digits
from sklearn.preprocessing import StandardScaler
import torch
import torch.nn as nn
from torch.onnx.symbolic_opset9 import tensor
from sklearn.model_selection import train_test_split

x,y = load_digits(return_X_y=True)
x = StandardScaler().fit_transform(x)

x = torch.tensor(x,dtype=torch.float)
y = torch.tensor(y,dtype=torch.float).reshape(-1,1)
print(x.shape)
print(y.shape)

x_train,x_test,y_train,y_test = train_test_split(x,y,random_state=42,shuffle=True,test_size=0.2)
print(x_train.shape)
print(y_train.shape)
# 2.模型构建需求：
# 必须构建一个多层感知机（MLP）模型，该模型应至少包含两个全连接层，且需通过torch.nn.Sequential进行有序构建。
# 模型的输入层应设计为64个神经元，以对应数据集的特征数量；第一隐藏层设计为128个神经元；输出层则设计为10个神经元，以代表0-9这10个数字类别。
# 模型中必须包含激活函数，以增强模型的非线性表达能力，推荐使用torch.nn.ReLU函数。
class MLP(nn.Module):
    def __init__(self,*args,**kwargs):
        super(MLP,self).__init__(*args,**kwargs)
        self.cv = nn.Sequential(
            nn.Linear(in_features=x_train.shape[1],out_features=128),
            nn.ReLU(),
            nn.Linear(in_features=128,out_features=64),
            nn.ReLU(),
            nn.Linear(in_features=64,out_features=10)
        )
    def forward(self,x):
        x = self.cv(x)
        return x
# 3.模型训练需求：
# 模型的训练过程需采用交叉熵损失函数（torch.nn.CrossEntropyLoss）来评估预测结果与实际标签之间的差异。
# 必须使用Adam优化器（torch.optim.Adam）对模型参数进行优化，且学习率需设置为0.01。
# 模型的训练次数应至少为200次，但也可以根据实际需求进行适当调整。
# 在训练过程中，每完成10次迭代，需打印一次当前的损失值和测试集上的准确率，以便实时监控模型的训练效果。
model = MLP()
loss_fn = nn.CrossEntropyLoss()
opt = torch.optim.Adam(model.parameters(),lr=0.01)
loss_list = []
model.train()

for i in range(200):
    opt.zero_grad()
    y_pre = model(x_train)
    loss = loss_fn(y_pre,y_train)
    loss_list.append(loss.item())
    if i%10 == 0:
        print(i,loss.item())
        print((y_pre.argmax(dim=1) == y_train).float().mean())
    loss.backward()
    opt.step()
# 4.性能评估与结果可视化需求：
# 训练完成后，需在测试集上计算并打印模型的准确率，以评估模型的泛化能力。
# 需绘制训练过程中的损失值曲线，并清晰地标注出横轴为“Epoch”（迭代次数），纵轴为“Loss”（损失值），同时给出图表的标题。通过这张图表，可以直观地观察到模型在训练过程中的损失变化情况。
model.eval()
y_pred = model(x_test).argmax(dim=1)
print((y_pred == y_test).float().mean())

plt.plot(loss_list,x_label='Epoch',y_label='Loss')
plt.legend()
plt.show()
